local _G = _G
local addon, ns = ...
local profile = ns.profile
local cfg = {}
local RCfunc
if not ns.RCfunc then
	ns.RCfunc = {}
end
if not _G.LabelUI_mod then
	_G.LabelUI_mod = {}	
end
local point
local canScanCooldowns
--local playerclass =  select(2, UnitClass("player"))
local RaidCooldowns = CreateFrame("Frame",nil,UIParent)

--------------[[		Events		]]-------------

-- oRA2
local function UpdateState()
	local inRaidNow = UnitInRaid("player")
	if not RaidCooldowns:IsShown() and inRaidNow then
		RaidCooldowns:Show()
	elseif RaidCooldowns:IsShown() and not inRaidNow then
		RaidCooldowns:Hide()
	end
end


local curcooldowns = {}
local barspos = {}
local bar = {}

local function updateBarsPos()
	if #(barspos) > 0 then
		local prevframe
		for i,v in pairs(barspos) do
			v:ClearAllPoints()
			if i == 1 then
				v:SetPoint("TOP", RaidCooldowns, "TOP", 0, 0)
				prevframe = v
			else
				v:SetPoint("TOP", prevframe, "BOTTOM", 0, 0)
				prevframe = v
			end					
		end	 
	end
end 

local function DellBar(self)
	for i,v in pairs(barspos) do
		if v == self then 
			tremove(barspos, i)
			break 
		end
	end
end
  
local function AddBar(self, endtime)
	if tContains(barspos, self) then
		DellBar(self)
	end
	self.stopped = false
	if #(barspos) == 0 then	
		self:SetPoint("TOP", RaidCooldowns, "TOP", 0, 0)
		tinsert(barspos, self)
	else
		local insert = false
		for i,v in pairs(barspos) do
			if v.endtime and endtime > v.endtime then
				tinsert(barspos, i, self)
				insert = true
				break
			end						
		end
		if not insert then
			tinsert(barspos, self)
		end
	end
end

local function OnUpdate(self)
	--print("HERE")
	if self:IsShown() and not self.stopped then
		self.text:Show()
		local loctime = self.endtime - GetTime()
		self.statusbar:SetValue(loctime)
		if not self.lastTime then self.lastTime = loctime + 1 end
		if (self.lastTime - loctime > 0.1) or (self.lastTime < loctime) then
			if loctime > 60 then
				local sektime = math.floor(mod(loctime, 60))
				self.text:SetText(tostring(math.floor((loctime)/60))..":"..tostring(math.floor(sektime/10))..tostring(math.floor(mod(sektime, 10))))
				self.lastTime = loctime
			else
				self.text:SetText(tostring(math.floor(loctime)).."."..tostring(math.floor(mod(loctime*10, 10))))
				self.lastTime = loctime
			end
		end
		if loctime < 0.1 then
			if self:IsShown() and not self.alphaOut:IsPlaying() then
				self.alphaOut:Play()	
				self.stopped = true			
			end
			self.text:Hide()	
			self.lastTime = nil				
		end
	end
end

local function CreateBar(id)
    local statusbar = cfg.statusbar
  	bar[id] = CreateFrame("Frame", nil, RaidCooldowns)
	bar[id].stopped = false
	bar[id]:SetPoint("CENTER", RaidCooldowns, "CENTER", 0, 0)
  	bar[id]:SetHeight(cfg.height)
 	bar[id]:SetWidth(cfg.width)
  	bar[id].icon = bar[id]:CreateTexture(nil, "BORDER")
  	bar[id].icon:SetPoint('TOPLEFT',bar[id],'TOPLEFT', 1.5, -1.5)
  	bar[id].icon:SetPoint('BOTTOMRIGHT',bar[id],'BOTTOMLEFT', cfg.height - 1.5, 1.5) 
  	bar[id].icon:SetTexCoord(0.1,0.9,0.1,0.9)
  	bar[id].statusbar = CreateFrame('StatusBar', nil, bar[id]) 
  	bar[id].statusbar:SetPoint('TOPLEFT',bar[id],'TOPLEFT', cfg.height, -1.5)
  	bar[id].statusbar:SetPoint('BOTTOMRIGHT',bar[id],'BOTTOMRIGHT', -1.5, 1.5)
  	bar[id].statusbar:SetBackdropColor(0.18,0.18,0.18, 0.8)
  	bar[id].statusbar:SetStatusBarTexture(statusbar) 
	--local color = cfg.classcolor
  	--bar[name].statusbar:SetStatusBarColor(color.r, color.g, color.b)
  	bar[id].iconBG = bar[id]:CreateTexture(nil, "BACKGROUND")
  	bar[id].iconBG:SetAllPoints(bar[id])
  	bar[id].iconBG:SetTexture(0, 0, 0, 0.4)
  	bar[id].text = bar[id].statusbar:CreateFontString(nil, 'OVERLAY') 
  	bar[id].text:SetFont(cfg.font, cfg.textsize)
  	bar[id].text:SetShadowOffset(1,-1)
  	bar[id].text:SetJustifyH('RIGHT')
  	bar[id].text:SetPoint('RIGHT', bar[id].statusbar,'RIGHT', -2, 0)
	bar[id].text:Hide()
  	bar[id].text:SetText("")
  	bar[id].text2 = bar[id].statusbar:CreateFontString(nil, 'OVERLAY') 
  	bar[id].text2:SetFont(cfg.font, cfg.textsize)
  	bar[id].text2:SetShadowOffset(1,-1)
  	bar[id].text2:SetJustifyH('LEFT')
  	bar[id].text2:SetPoint('LEFT', bar[id].statusbar,'LEFT', 2, 0)
	bar[id].text2:SetText("")
	bar[id].id = id
	local anim = bar[id]:CreateAnimationGroup()
	local alphaOut = anim:CreateAnimation("Alpha")
	alphaOut:SetChange(-1)
	alphaOut:SetDuration(3)
	alphaOut:SetOrder(1)
	anim:SetScript("OnFinished", function() 
		DellBar(bar[id])
		updateBarsPos()
		local loctime = bar[id].endtime - GetTime()
		if ((loctime < 0.1) and bar[id]:IsShown()) then 
			bar[id]:Hide() 
			bar[id].free = true 
			curcooldowns[bar[id].cooldownname][bar[id].cooldownid ] = nil
		end 
	end)
	bar[id].alphaOut = anim
	bar[id]:SetScript("OnUpdate", OnUpdate)
	bar[id]:Hide()
end

local curmaxid = 1

local function CreateOrUpdateBar(Name, spellId, cd, c)
	local function UpdateBar(id)
		bar[id].free = false
		bar[id].endtime = GetTime() + cd
		local _, _, texture = GetSpellInfo(spellId)
		bar[id].icon:SetTexture(texture)
		bar[id].text2:SetText(Name)
		bar[id].statusbar:SetMinMaxValues(0, cd)
		local color = RAID_CLASS_COLORS[c]
		if color then
  			bar[id].statusbar:SetStatusBarColor(color.r, color.g, color.b)
		end
		bar[id]:Show()
		AddBar(bar[id], bar[id].endtime)
		updateBarsPos()	
		if not curcooldowns[Name] then
			curcooldowns[Name] = {}
		end 
		curcooldowns[Name][spellId] = id
		bar[id].cooldownid = spellId
		bar[id].cooldownname = Name		
	end 
	if curcooldowns[Name] and curcooldowns[Name][spellId] then
		local id = curcooldowns[Name][spellId]
		bar[id].endtime = GetTime() + cd
		bar[id].alphaOut:Stop() 
		AddBar(bar[id], bar[id].endtime)
		updateBarsPos()
	else
		local id
		for i,v in pairs(bar) do
			if v.free then
				id = v.id
			end
		end
		if id then
			UpdateBar(id)
		else
			if curmaxid <= 20 then
				id = curmaxid 
				curmaxid = curmaxid + 1
				CreateBar(id)
				UpdateBar(id)
			end
		end
	end  
end

local function StartCooldown(Name, spellId, cd, c) 
	for i,v in pairs(RaidCooldowns.cooldowns) do
		local sname = GetSpellInfo(spellId)
		if v[sname] and v[sname].id == spellId then
			CreateOrUpdateBar(Name, spellId, cd, c)
		end		
	end	
end

local function CombatEvent(self, event, ...)
	local eventType,srcName, dstName, spellId, spellName		
	local weare42 = tonumber((select(4, GetBuildInfo()))) > 40100
	if weare42 then
		_, eventType, _, _, srcName, _,_, _, dstName, _,_, spellId, spellName, _ = ...
	else
		_, eventType, _, _, srcName, _, _, dstName, _, spellId, spellName, _ = ...
	end
	if eventType ~= "SPELL_CAST_SUCCESS" and eventType ~= "SPELL_RESURRECT" then return end
	if not srcName or srcName == UnitName("player") then return end
	
	local _, c = UnitClass(srcName)
	if RaidCooldowns.cooldowns[c] then
		local spells = RaidCooldowns.cooldowns[c]
		if spells[spellName] and spells[spellName] then
			StartCooldown(srcName, spellId, spells[spellName].cd, c)
			return
		end
	end
	
	return
end

--------------[[		Comm Methods		]]--------------

local function OnCommReceived(prefix, msg, distro, sender)
	--if sender == playerName then return end
	--self:Print(prefix.."[]"..msg)
	local spellId, cooldown = 0, 0
	
	if prefix == "oRA" or prefix == "CTRA" then
		spellId, cooldown = select(3, msg:find("CD (%d) (%d+)"))
		spellId  = tonumber(spellId)
		cooldown = tonumber(cooldown)
		
		if spellId == 0 or cooldown == 0 then return end
		
		local _, c = UnitClass(sender)
		if RaidCooldowns.cooldowns[c] then
			for k, v in pairs(RaidCooldowns.cooldowns[c]) do
				if v.ora and v.ora == spellId then -- spellId is, in this case, the oRA ID
					spellId = v.id
					StartCooldown(sender, spellId, v.cd, c)
					return
				end
			end
		end
	elseif (prefix == "oRA3" and (distro == "RAID" or distro == "PARTY")) then
		ok, commtype, spell, cd = RCfunc.Deserialize(msg)
		--self:Print(commtype, spell, cd)
		if ok and type(spell) == "number" and type(cd) == "number" and commtype == "Cooldown" and sender ~= UnitName("player") then
			--self:Print(commtype, spell, cd)
			local _, c = UnitClass(sender)
			StartCooldown(sender, spell, cd, c)
		end		
	else
		--[===[@debug@
		--self:Print("OnCommReceived(", prefix, msg, distro, sender, ")")
		--@end-debug@]===]
		spellId, cooldown = select(3, msg:find("(%d+) (%d+)"))
		spellId  = tonumber(spellId)
		cooldown = tonumber(cooldown)
		local _, c = UnitClass(sender)
		
		if cooldown == 0 then
			--StopCooldown(sender, spellId)
		else
			--print('he-he')
			StartCooldown(sender, spellId, cooldown, c)
		end
	end
end

--------------[[		Combat Events		]]--------------

local function GenericSpellSuccess(self, event, unit, spell, ...)
	if unit ~= "player" then return end
	local playerclass =  select(2, UnitClass("player"))
	if RaidCooldowns.cooldowns[playerclass][spell] == nil then return end
	local _, _, id = ...
	if RaidCooldowns.cooldowns[playerclass][spell].id ~= id then return end
	canScanCooldowns = true
end

local function GetCooldown(spell)
	local remaining = 0
	local startTime, cooldown, enabled = GetSpellCooldown(spell)

	if startTime and startTime > 0 and cooldown >= 2 and enabled == 1 then
		remaining = math.ceil(startTime + cooldown - GetTime())
	end
	
	return remaining
end

--------------[[		Comm Methods		]]--------------

local function Sync(data, cooldown)
	if cooldown == nil then return end	
	local id = data
	if type(data) == "table" then
		id = data.id
		if not oRA and data.ora ~= nil then
			RCfunc.SendCommMessage("oRA", "CD " .. data.ora .. " " .. (cooldown / 60), "RAID")
		end
	end
	if cooldown ~= 0 then
		RCfunc.SendCommMessage("oRA3", RCfunc.Serialize("Cooldown", id, cooldown), "RAID")
	end
	RCfunc.SendCommMessage(RaidCooldowns.prefix, (id .. " " .. cooldown), "RAID")
end

local function ScanSpells()
	if canScanCooldowns == false then return end
	
	local cooldown
	local playerclass =  select(2, UnitClass("player"))
	for k, v in pairs(RaidCooldowns.cooldowns[playerclass]) do
		cooldown = GetCooldown(v.id)
		if cooldown == 0 or cooldown > 2 then
			--print(cooldown, tostring(v.id),k)
			Sync(v, cooldown)
		end
	end
	
	canScanCooldowns = false
end

local function Respec(event)
	RaidCooldowns.canScanCooldowns = true
	ScanSpells()
end

local function Init()
	for i,v in pairs(cfg.hide) do
		if RaidCooldowns.cooldowns[i] and RaidCooldowns.cooldowns[i][v] then
			RaidCooldowns.cooldowns[i][v] = nil
		end		
	end
	RCfunc = ns.RCfunc
	RaidCooldowns:UnregisterAllEvents()
	RaidCooldowns.prefix = "RCD2"
	RaidCooldowns:SetWidth(cfg.width)
	RaidCooldowns:SetHeight(1)
	RaidCooldowns:SetPoint(cfg.pos.a1,point(cfg.pos.af),cfg.pos.a2,cfg.pos.x,cfg.pos.y)
	RCfunc.RegisterComm(RaidCooldowns.prefix, OnCommReceived)
	RCfunc.RegisterComm("oRA", OnCommReceived)
	RCfunc.RegisterComm("CTRA", OnCommReceived)
	RCfunc.RegisterComm("oRA3", OnCommReceived)
	RaidCooldowns:RegisterEvent("COMBAT_LOG_EVENT_UNFILTERED")
	RaidCooldowns:RegisterEvent("GROUP_ROSTER_UPDATE")
	RaidCooldowns:RegisterEvent("CHARACTER_POINTS_CHANGED")
	RaidCooldowns:RegisterEvent("CONFIRM_TALENT_WIPE")
	RaidCooldowns:RegisterEvent("PLAYER_TALENT_UPDATE")
	RaidCooldowns:RegisterEvent("UNIT_SPELLCAST_SUCCEEDED")
	RaidCooldowns:RegisterEvent("SPELL_UPDATE_COOLDOWN")
	UpdateState()
	canScanCooldowns = true
	ScanSpells()
end

function RaidCooldowns.InitMod(main, profilename)
	if not profile[profilename] then
		for i,v in pairs(profile) do
			if type(i) == "string" then
				profilename = i
				break
			end
		end
	end
	for i,v in pairs(profile[profilename]) do
		cfg[i] = v		
	end
	ns.cfg = cfg
	point = main.point  
	main:AddInit(Init, "Addon RaidCooldowns")
end 

RaidCooldowns:SetScript("OnEvent", function(self, event, ...)
 	if event == "COMBAT_LOG_EVENT_UNFILTERED" and RaidCooldowns:IsShown() then
		CombatEvent(self, event, ...)
	elseif event == "GROUP_ROSTER_UPDATE" then 
		UpdateState()
	elseif event == "CHARACTER_POINTS_CHANGED" or event == "CONFIRM_TALENT_WIPE" or  event == "PLAYER_TALENT_UPDATE" then
		Respec(self, event, ...)
	elseif event == "UNIT_SPELLCAST_SUCCEEDED" then
		GenericSpellSuccess(self, event, ...)
	elseif event == "SPELL_UPDATE_COOLDOWN"  then
		ScanSpells(self, event, ...)
	end 
 end)

_G.LabelUI_mod["RaidCooldowns"] = RaidCooldowns